package com.gujiangbo.application.common.conf;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySources;
import org.springframework.stereotype.Component;

@Component
@PropertySources({
		@org.springframework.context.annotation.PropertySource({ "classpath:config/base/common-conf.properties" }) })
public abstract class ConfProxy {
	private Map<Field, String> propKeyMap;
	private Map<String, String> cacheMap;
	private Map<String, NodeCache> nodeCacheMap;
	private String rootPath = "";
	@Autowired
	@Qualifier("confClient")
	private ConfClient confClient;
	@Value("${common-conf.zk.ns}")
	private String _NAMESPACE;
	@Value("${common-conf.zk.cli.key}")
	private String _CLIENT_KEY;
	@Value("${common-conf.enable}")
	private boolean enable;

	@PostConstruct
	protected void initBean() throws Exception {
		if (this.nodeCacheMap == null) {
			this.nodeCacheMap = new HashMap();
		}
		ConfCache classConf = (ConfCache) getClass().getAnnotation(ConfCache.class);
		if (classConf != null) {
			this.rootPath = classConf.key();
		}
		if ((this.rootPath != null) && (this.rootPath.length() > 0)) {
			this.rootPath += ".";
		}
		initPropKeyMap();
		initConfCacheFields();
	}

	@PreDestroy
	protected void preDestory() {
		if (!this.enable) {
			return;
		}
		for (NodeCache nodeCache : this.nodeCacheMap.values()) {
			try {
				nodeCache.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void initPropKeyMap() {
		if (this.propKeyMap == null) {
			this.propKeyMap = new HashMap();
		}
		Field[] fields = getClass().getDeclaredFields();
		Field[] arrayOfField1;
		int j = (arrayOfField1 = fields).length;
		for (int i = 0; i < j; i++) {
			Field field = arrayOfField1[i];
			if (field.isAnnotationPresent(ConfCache.class)) {
				this.propKeyMap.put(field, this.rootPath + ((ConfCache) field.getAnnotation(ConfCache.class)).key());
			}
		}
	}

	private void initConfCacheFields() throws Exception {
		for (Field field : this.propKeyMap.keySet()) {
			String key = (String) this.propKeyMap.get(field);
			String value = null;
			if (this.enable) {
				value = addConfCache(key);
			}
			field.setAccessible(true);
			if (!Strings.isNullOrEmpty(value)) {
				BeanUtils.setProperty(this, field.getName(), JSONObject.parseObject(value, field.getType()));
			} else if (!Strings.isNullOrEmpty(((ConfCache) field.getAnnotation(ConfCache.class)).default_value())) {
				BeanUtils.setProperty(this, field.getName(),
						((ConfCache) field.getAnnotation(ConfCache.class)).default_value());
			}
			field.setAccessible(false);
		}
	}

	public boolean saveByKey(String key, Object value) {
		boolean result = true;
		if (this.enable) {
			result = this.confClient.save(key, JSONObject.toJSONString(value));
		}
		if (result) {
			this.cacheMap.put(key, JSONObject.toJSONString(value));
		}
		return result;
	}

	public boolean saveByProperty(String property, Object value)
			throws NoSuchFieldError, IllegalAccessException, InvocationTargetException {
		Field targetField = null;
		for (Field field : this.propKeyMap.keySet()) {
			if (field.getName().equals(property)) {
				targetField = field;
				break;
			}
		}
		if (targetField == null) {
			throw new NoSuchFieldError();
		}
		String key = (String) this.propKeyMap.get(targetField);
		if (key == null) {
			return false;
		}
		boolean result = saveByKey(key, value);
		if (result) {
			BeanUtils.copyProperty(this, property, value);
		}
		return result;
	}

	public String getFromCache(String key) {
		return (String) this.cacheMap.get(key);
	}

	private String addConfCache(final String key) throws Exception {
		if (this.cacheMap == null) {
			this.cacheMap = new HashMap();
		}
		if (this.cacheMap.containsKey(key)) {
			return (String) this.cacheMap.get(key);
		}
		final NodeCache nodeCache = new NodeCache(this.confClient.getCli(), this.confClient.getNodePathByKey(key));
		nodeCache.start(true);
		this.nodeCacheMap.put(key, nodeCache);

		nodeCache.getListenable().addListener(new NodeCacheListener() {
			public void nodeChanged() throws Exception {
				String newStringValue = null;
				if (nodeCache.getCurrentData() != null) {
					newStringValue = new String(nodeCache.getCurrentData().getData());
				}
				ConfProxy.this.cacheMap.put(key, newStringValue);
				for (Field field : ConfProxy.this.propKeyMap.keySet()) {
					String theKey = (String) ConfProxy.this.propKeyMap.get(field);
					if (key.equals(theKey)) {
						ConfProxy.this.setConfCacheField(field, newStringValue);
					}
				}
			}
		});
		if (nodeCache.getCurrentData() == null) {
			return null;
		}
		String value = new String(nodeCache.getCurrentData().getData());
		this.cacheMap.put(key, value);

		return value;
	}

	private void setConfCacheField(Field field, String strValue) throws Exception {
		field.setAccessible(true);

		BeanUtils.setProperty(this, field.getName(), JSONObject.parseObject(strValue, field.getType()));
		field.setAccessible(false);
	}
}